﻿using System;
using System.IO;
using MLSharp;

namespace MLSharp.See5
{
	/// <summary>
	/// Generates data ready for consumption by See5 from
	/// a <see cref="ExcelDataSet"/>.
	/// </summary>
	public class See5DataWriter
	{
		#region Private Fields

		/// <summary>
		/// The name of the data set (See5 filestem)
		/// </summary>
		private string mDataSetName;

		/// <summary>
		/// The attribute to try and predict.
		/// </summary>
		private string mTargetAttribute;

		#endregion

		#region Private Methods

		/// <summary>
		/// Writes the See5 .names file.
		/// </summary>
		/// <param name="namesFilePath">The path to the .names file to create.</param>
		/// <param name="dataSet">The data set to use.</param>
		/// <param name="ignoredAttributeIndexes">The indexes of any attributes that should be ignored by See5.</param>
		private void WriteNamesFile(IDataSet dataSet, string namesFilePath, params int[] ignoredAttributeIndexes)
		{
			using (StreamWriter writer = File.CreateText(namesFilePath))
			{
				//Step 1: write the target attribute
				writer.WriteLine("{0}.", mTargetAttribute);
				writer.WriteLine();

				//Step 2: write each attribute and a description for it.

				//Start with the label
				writer.WriteLine("label:\t\tlabel.");

				for (int i=0; i < dataSet.Attributes.Count; i++)
				{
					DataAttribute attribute = dataSet.Attributes[i];

					writer.Write("{0}:\t\t", attribute.Name);

					if (ignoredAttributeIndexes != null && Array.IndexOf(ignoredAttributeIndexes, i) >= 0)
					{
						writer.Write("ignore");
					}
					else
					{
						switch (attribute.Type)
						{
							case AttributeType.Continuous:
								writer.Write("continuous");
								break;

							case AttributeType.Discrete:
								writer.Write("discrete {0}", attribute.DiscreteValueCount);
								break;

							case AttributeType.Set:
								writer.Write(string.Join(", ", attribute.PossibleValues));
								break;
						}
					}

					writer.WriteLine(".");
				}

				writer.Close();
			}
		}

		/// <summary>
		/// Writes the See5 data file.
		/// </summary>
		/// <param name="dataFilePath">The path to the .data file to create.</param>
		/// <param name="dataSet">The data set to use.</param>
		private void WriteDataFile(IDataSet dataSet, string dataFilePath)
		{
			using (StreamWriter writer = File.CreateText(dataFilePath))
			{
				foreach (Instance instance in dataSet.Instances)
				{
					//We need a spot for each value plus the label.
					string[] cleanedValues = new string[instance.Values.Length+1];

					//Write the label at the first position.
					if (string.IsNullOrEmpty(instance.Label))
					{
						instance.Label = "n/a";
					}

					cleanedValues[0] = instance.Label.Replace(",", "");

					for (int i = 0; i < cleanedValues.Length-1; i++ )
					{
						//If the value is missing, write a '?', which is the placeholder for a missing value.
						if (instance.IsValueMissing(i))
						{
							cleanedValues[i+1] = "?";
						}
						//If it is numeric, just write the raw value
						else if (dataSet.Attributes[i].IsNumeric())
						{
							cleanedValues[i+1] = instance.Values[i].ToString();
						}
						//Otherwise, write the string representation of the item
						else
						{
							cleanedValues[i + 1] = instance.GetStringValue(i).Replace(",", "");
						}
					}

					writer.WriteLine("{0}.", string.Join(",", cleanedValues));
				}

				writer.Close();
			}
		}

		#endregion

		#region Public Constructors

		/// <summary>
		/// Constructs an instance of the writer.
		/// </summary>
		/// <param name="dataSetName">The name of the data set, 
		/// will be used as the See5 filestem.</param>
		/// <param name="targetAttribute">The attribute to try and induce.</param>
		public See5DataWriter(string dataSetName, string targetAttribute)
		{
			mDataSetName = dataSetName;
			mTargetAttribute = targetAttribute;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Writes the a See5-consumable version of the data set to the output directory.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="outputDirectory">The directory to write to.</param>
		/// <param name="ignoredAttributeIndexes">The indexes for any attributes in the data set that should be ignored.</param>
		public void WriteNamesAndData(IDataSet dataSet, string outputDirectory, params int[] ignoredAttributeIndexes)
		{
			if (outputDirectory != string.Empty && !Directory.Exists(outputDirectory))
				throw (new DirectoryNotFoundException(outputDirectory + " does not exist or is in accessible."));

			string namesFile = Path.Combine(outputDirectory, mDataSetName + ".names");

			WriteNamesFile(dataSet, namesFile, ignoredAttributeIndexes);

			string dataFile = Path.Combine(outputDirectory, mDataSetName + ".data");

			WriteDataFile(dataSet, dataFile);
		}

		/// <summary>
		/// Creates a filestem.cases file for use in classification.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="outputDirectory">The directory to write to.</param>
		public void WriteCases(IDataSet dataSet, string outputDirectory)
		{
			if (!Directory.Exists(outputDirectory))
				throw (new DirectoryNotFoundException(outputDirectory + " does not exist or is in accessible."));


			string casesFile = Path.Combine(outputDirectory, mDataSetName + ".cases");

			WriteDataFile(dataSet, casesFile);
		}

		#endregion
	}
}
